package com.boil.qz.safekavass.weixin.util;

import com.alibaba.fastjson.JSONObject;
import com.boil.qz.safekavass.pojo.WeixinGethbinfoParamVo;
import com.boil.qz.safekavass.pojo.WeixinSendredpackParamVo;
import com.boil.qz.safekavass.weixin.WeixinException.WeixinException;
import com.boil.qz.safekavass.weixin.aes.WXBizMsgCrypt;
import com.boil.qz.safekavass.weixin.model.reply.Article;
import com.boil.qz.safekavass.weixin.model.reply.BaseMsg;
import com.boil.qz.safekavass.weixin.model.reply.NewsMsg;
import com.boil.util.Constants;
import com.boil.util.FastJsonUtils;
import com.boil.util.MyXppDriver;
import com.boil.util.Utils;
import com.thoughtworks.xstream.XStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.URL;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

/**
 * Created by ChenYong on 2017-04-27.
 * <br>
 * 微信工具类（核心家伙）。
 */
public class WeixinUtils {
    /**
     * 日志
     */
    private static Logger log = LoggerFactory.getLogger(WeixinUtils.class);

    //--------------------[应用安全常量]--------------------//
    /**
     * 应用 ID
     */
    public static final String APP_ID = "wxef6f28b9cf18a9db";
    /**
     * 应用密钥
     */
    public static final String APP_SECRET = "59f3ab7b2f400650de9cb2d9cb3b6316";
    /**
     * 令牌
     */
    public static final String TOKEN = "qz_boil";
    /**
     * 消息加解密密钥
     */
    public static final String ENCODING_AES_KEY = "ZMiySPXx7lyK3sD4YFu0G2t9JqJJi932cOYuWzM1nZW";
    /**
     * 微信支付密钥
     */
    public static final String WEIXIN_PAY_KEY = "823a9abc6079488b96f614ddfa4b2813";
    //--------------------[/应用安全常量]--------------------//

    //--------------------[常量]--------------------//
    /**
     * ToUserName
     */
    public static final String TO_USER_NAME_STR = "ToUserName";
    /**
     * FromUserName
     */
    public static final String FROM_USER_NAME_STR = "FromUserName";
    /**
     * snsapi_base
     */
    public static final String SCOPE_SNSAPI_BASE = "snsapi_base";
    /**
     * snsapi_userinfo
     */
    public static final String SCOPE_SNSAPI_USERINFO = "snsapi_userinfo";
    /**
     * openid
     */
    public static final String OPENID_STR = "openid";
    /**
     * access_token
     */
    public static final String ACCESS_TOKEN_STR = "access_token";
    /**
     * expires_in
     */
    public static final String EXPIRES_IN_STR = "expires_in";
    /**
     * refresh_token
     */
    public static final String REFRESH_TOKEN_STR = "refresh_token";
    /**
     * errcode
     */
    public static final String ERRCODE_STR = "errcode";
    /**
     * errmsg
     */
    public static final String ERRMSG_STR = "errmsg";

    /**
     * click
     */
    public static final String CLICK_STR = "click";
    /**
     * view
     */
    public static final String VIEW_STR = "view";
    /**
     * xml
     */
    public static final String XML_STR = "xml";
    /**
     * SUCCESS
     */
    public static final String SUCCESS_STR = "SUCCESS";
    /**
     * FAIL
     */
    public static final String FAIL_STR = "FAIL";
    /**
     * 发红包成功
     */
    public static final String SUCCESS_SENDREDPACK_MSG = "发红包成功";
    /**
     * total
     */
    public static final String TOTAL_STR = "total";
    /**
     * count
     */
    public static final String COUNT_STR = "count";
    /**
     * data
     */
    public static final String DATA_STR = "data";
    /**
     * next_openid
     */
    public static final String NEXT_OPENID_STR = "next_openid";
    //--------------------[/常量]--------------------//

    //--------------------[用户基本信息常量]--------------------//
    /**
     * subscribe
     */
    public static final String SUBSCRIBE_STR = "subscribe";
    /**
     * nickname
     */
    public static final String NICKNAME_STR = "nickname";
    /**
     * sex
     */
    public static final String SEX_STR = "sex";
    /**
     * language
     */
    public static final String LANGUAGE_STR = "language";
    /**
     * city
     */
    public static final String CITY_STR = "city";
    /**
     * province
     */
    public static final String PROVINCE_STR = "province";
    /**
     * country
     */
    public static final String COUNTRY_STR = "country";
    /**
     * headimgurl
     */
    public static final String HEADIMGURL_STR = "headimgurl";
    /**
     * subscribe_time
     */
    public static final String SUBSCRIBE_TIME_STR = "subscribe_time";
    /**
     * unionid
     */
    public static final String UNIONID_STR = "unionid";
    /**
     * remark
     */
    public static final String REMARK_STR = "remark";
    /**
     * groupid
     */
    public static final String GROUPID_STR = "groupid";
    //--------------------[/用户基本信息常量]--------------------//

    //--------------------[发红包常量]--------------------//
    /**
     * 微信支付商户号
     */
    public static final String MCH_ID = "1471451702";
    /**
     * 商户名称
     */
    public static final String SEND_NAME = "清镇安监卫士";
    /**
     * 红包祝福语
     */
    public static final String WISHING = "感谢您的举报，祝您工作愉快，我们将认真处理！";
    /**
     * Ip地址
     */
    public static final String CLIENT_IP = "121.40.96.135";
    /**
     * 活动名称
     */
    public static final String ACT_NAME = "清镇安监举报活动";
    /**
     * 场景id：
     * <br>
     * PRODUCT_4：企业内部福利。
     */
    public static final String SCENE_ID_PRODUCT_4 = "PRODUCT_4";
    /**
     * nonce_str
     */
    public static final String NONCE_STR_STR = "nonce_str";
    /**
     * sign
     */
    public static final String SIGN_STR = "sign";
    /**
     * mch_billno
     */
    public static final String MCH_BILLNO_STR = "mch_billno";
    /**
     * mch_id
     */
    public static final String MCH_ID_STR = "mch_id";
    /**
     * wxappid
     */
    public static final String WXAPPID_STR = "wxappid";
    /**
     * send_name
     */
    public static final String SEND_NAME_STR = "send_name";
    /**
     * re_openid
     */
    public static final String RE_OPENID_STR = "re_openid";
    /**
     * total_amount
     */
    public static final String TOTAL_AMOUNT_STR = "total_amount";
    /**
     * total_num
     */
    public static final String TOTAL_NUM_STR = "total_num";
    /**
     * wishing
     */
    public static final String WISHING_STR = "wishing";
    /**
     * client_ip
     */
    public static final String CLIENT_IP_STR = "client_ip";
    /**
     * act_name
     */
    public static final String ACT_NAME_STR = "act_name";
    /**
     * scene_id
     */
    public static final String SCENE_ID_STR = "scene_id";
    /**
     * risk_info
     */
    public static final String RISK_INFO_STR = "risk_info";
    /**
     * consume_mch_id
     */
    public static final String CONSUME_MCH_ID_STR = "consume_mch_id";
    /**
     * return_code
     */
    public static final String RETURN_CODE_STR = "return_code";
    /**
     * return_msg
     */
    public static final String RETURN_MSG_STR = "return_msg";
    /**
     * result_code
     */
    public static final String RESULT_CODE_STR = "result_code";
    /**
     * err_code
     */
    public static final String ERR_CODE_STR = "err_code";
    /**
     * err_code_des
     */
    public static final String ERR_CODE_DES_STR = "err_code_des";
    /**
     * send_listid
     */
    public static final String SEND_LISTID_STR = "send_listid";
    /**
     * appid
     */
    public static final String APPID_STR = "appid";
    /**
     * bill_type
     */
    public static final String BILL_TYPE_STR = "bill_type";
    /**
     * detail_id
     */
    public static final String DETAIL_ID_STR = "detail_id";
    /**
     * status
     */
    public static final String STATUS_STR = "status";
    /**
     * send_type
     */
    public static final String SEND_TYPE_STR = "send_type";
    /**
     * hb_type
     */
    public static final String HB_TYPE_STR = "hb_type";
    /**
     * reason
     */
    public static final String REASON_STR = "reason";
    /**
     * send_time
     */
    public static final String SEND_TIME_STR = "send_time";
    /**
     * refund_time
     */
    public static final String REFUND_TIME_STR = "refund_time";
    /**
     * refund_amount
     */
    public static final String REFUND_AMOUNT_STR = "refund_amount";
    /**
     * amount
     */
    public static final String AMOUNT_STR = "amount";
    /**
     * rcv_time
     */
    public static final String RCV_TIME_STR = "rcv_time";
    /**
     * 订单类型
     */
    public static final String BILL_TYPE_MCHT = "MCHT";
    /**
     * 红包状态：发放中
     */
    public static final String STATUS_SENDING = "SENDING";
    /**
     * 红包状态：已发放待领取
     */
    public static final String STATUS_SENT = "SENT";
    /**
     * 红包状态：发放失败
     */
    public static final String STATUS_FAILED = "FAILED";
    /**
     * 红包状态：已领取
     */
    public static final String STATUS_RECEIVED = "RECEIVED";
    /**
     * 红包状态：退款中
     */
    public static final String STATUS_RFUND_ING = "RFUND_ING";
    /**
     * 红包状态：已退款
     */
    public static final String STATUS_REFUND = "REFUND";
    //--------------------[/发红包常量]--------------------//

    //--------------------[加密安全常量]--------------------//
    /**
     * SSL
     */
    private static final String SSL = "SSL";
    /**
     * SunJSSE
     */
    private static final String SUN_JSSE = "SunJSSE";
    /**
     * Encrypt
     */
    public static final String ENCRYPT_STR = "Encrypt";
    /**
     * 时间戳字符串
     */
    public static final String TIMESTAMP_STR = "timestamp";
    /**
     * 随机数字符串
     */
    public static final String NONCE_STR = "nonce";
    /**
     * 加密类型字符串
     */
    public static final String ENCRYPT_TYPE_STR = "encrypt_type";
    /**
     * 消息加密签名字符串
     */
    public static final String MSG_SIGNATURE_STR = "msg_signature";
    /**
     * AES 加密
     */
    public static final String ENCRYPT_TYPE_AES = "aes";
    /**
     * 微信指定的加密密文 XML 字符串格式
     */
    private static final String ENCRYPT_XML_FORMAT = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
    /**
     * PKCS12
     */
    private static final String PKCS12 = "PKCS12";
    /**
     * 证书 apiclient_cert.p12 路径
     */
    private static final String APICLIENT_CERT_P12_PATH = "/cert/apiclient_cert.p12";
    /**
     * TLSv1
     */
    private static final String TLSV1 = "TLSv1";
    //--------------------[/加密安全常量]--------------------//

    //--------------------[消息类型常量]--------------------//
    /**
     * MsgType
     */
    public static final String MSG_TYPE_STR = "MsgType";
    /**
     * 消息类型：文本
     */
    public static final String MSG_TYPE_TEXT = "text";
    /**
     * 消息类型：图片
     */
    public static final String MSG_TYPE_IMAGE = "image";
    /**
     * 消息类型：语音
     */
    public static final String MSG_TYPE_VOICE = "voice";
    /**
     * 消息类型：视频
     */
    public static final String MSG_TYPE_VIDEO = "video";
    /**
     * 消息类型：音乐
     */
    public static final String MSG_TYPE_MUSIC = "music";
    /**
     * 消息类型：图文
     */
    public static final String MSG_TYPE_NEWS = "news";
    /**
     * 消息类型：小视频
     */
    public static final String MSG_TYPE_SHORTVIDEO = "shortvideo";
    /**
     * 消息类型：地理位置
     */
    public static final String MSG_TYPE_LOCATION = "location";
    /**
     * 消息类型：链接
     */
    public static final String MSG_TYPE_LINK = "link";
    /**
     * 消息类型：事件
     */
    public static final String MSG_TYPE_EVENT = "event";
    //--------------------[/消息类型常量]--------------------//

    //--------------------[事件类型常量]--------------------//
    /**
     * Event
     */
    public static final String EVENT_TYPE_STR = "Event";
    /**
     * 事件类型：关注
     */
    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    /**
     * 事件类型：取消关注
     */
    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    /**
     * 事件类型：扫描带参数二维码，前提是用户已关注公众账号
     */
    public static final String EVENT_TYPE_SCAN = "SCAN";
    /**
     * 事件类型：上报地理位置
     */
    public static final String EVENT_TYPE_LOCATION = "LOCATION";
    /**
     * 事件类型：点击自定义菜单
     */
    public static final String EVENT_TYPE_CLICK = "CLICK";
    /**
     * 事件类型：点击自定义菜单
     */
    public static final String EVENT_TYPE_VIEW = "VIEW";
    //--------------------[/事件类型常量]--------------------//

    //--------------------[接口地址]--------------------//
    /**
     * 获取 access_token 接口
     */
    public static final String URL_GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    /**
     * 获取微信服务器IP地址接口
     */
    public static final String URL_GET_CALLBACK_IP = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=%s";
    /**
     * 自定义菜单创建接口
     */
    public static final String URL_POST_CREATE_MENU = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s";
    /**
     * 获取用户基本信息（包括UnionID机制）接口
     */
    public static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN";
    /**
     * 获取用户列表接口
     */
    public static final String URL_GET_USER_LIST = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=%s&next_openid=%s";
    /**
     * 发普通红包接口
     */
    public static final String URL_POST_SENDREDPACK = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
    /**
     * 查询红包记录
     */
    public static final String URL_POST_GETHBINFO = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo";
    //--------------------[/接口地址]--------------------//

    //--------------------[网页授权接口地址]--------------------//
    /**
     * 用户同意授权，获取 code 接口
     */
    public static final String URL_CONNECT_OAUTH2_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
    /**
     * 通过 code 换取网页授权 access_token 接口
     */
    public static final String URL_SNS_OAUTH2_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
    /**
     * 刷新 access_token（如果需要）
     */
    public static final String URL_SNS_OAUTH2_REFRESH_TOKEN = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
    /**
     * 拉取用户信息（需 scope 为 snsapi_userinfo）
     */
    public static final String URL_SNS_USERINFO = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
    /**
     * 检验授权凭证（access_token）是否有效
     */
    public static final String URL_SNS_AUTH = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s ";
    //--------------------[/网页授权接口地址]--------------------//

    //--------------------[缓存]--------------------//
    /**
     * 凭证
     */
    public static String ACCESS_TOKEN = "";
    //--------------------[/缓存]--------------------//

    /**
     * SHA1 算法生成的微信加密签名：<br>
     * 1、将 token、timestamp、nonce 三个参数进行字典序排序；<br>
     * 2、将三个参数字符串拼接成一个字符串进行 sha1 加密；<br>
     * 3、开发者获得加密后的字符串可与 signature 对比，标识该请求来源于微信。<br>
     *
     * @param token     令牌
     * @param timestamp 时间戳
     * @param nonce     随机字符串
     * @return 微信加密签名
     * @throws WeixinException 抛出微信异常
     */
    public static String getSignature(String token, String timestamp, String nonce) throws WeixinException {
        try {
            if (StringUtils.isBlank(token) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonce)) {
                throw new WeixinException(String.format("SHA1算法生成微信加密签名的参数不能为空--->token[%s], timestamp[%s], nonce[%s]", token, timestamp, nonce));
            }

            // 参数数组
            String[] array = new String[]{token, timestamp, nonce};
            // 参数排序缓冲区
            StringBuffer sortSb = new StringBuffer();

            // 字符串排序
            Arrays.sort(array);

            for (int i = Constants.VALUE_0; i < array.length; i++) {
                sortSb.append(array[i]);
            }

            // 排序后的字符串
            String sortStr = sortSb.toString();
            // 获取 SHA1 加密算法
            MessageDigest sha1MessageDigest = MessageDigest.getInstance(Constants.SHA_1);
            // SHA1 加密
            sha1MessageDigest.update(sortStr.getBytes());

            return Utils.bytes2HexStr(sha1MessageDigest.digest());
        } catch (Exception e) {
            throw new WeixinException("SHA1算法生成的微信加密签名出现异常", e);
        }
    }

    /**
     * 校验微信加密签名：<br>
     * 1、将 token、timestamp、nonce 三个参数进行字典序排序；<br>
     * 2、将三个参数字符串拼接成一个字符串进行 sha1 加密；<br>
     * 3、开发者获得加密后的字符串可与 signature 对比，标识该请求来源于微信。<br>
     *
     * @param signature 微信加密签名，signature 结合了开发者填写的 token 参数和请求中的 timestamp 参数、nonce 参数
     * @param token     令牌
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @return <ol>
     * <li>接入成功，原样返回 echostr；</li>
     * <li>接入失败，返回“false”字符串；</li>
     * </ol>
     * @throws WeixinException 抛出微信异常
     */
    public static boolean checkSignature(String signature, String token, String timestamp, String nonce) throws WeixinException {
        try {
            if (StringUtils.isBlank(signature)) {
                throw new WeixinException(String.format("校验微信加密签名的参数不能为空--->signature[%s]", signature));
            }

            return signature.equals(getSignature(token, timestamp, nonce));
        } catch (Exception e) {
            throw new WeixinException("校验微信加密签名出现异常", e);
        }
    }

    /**
     * 发送 HTTPS 请求。
     *
     * @param url      url
     * @param method   请求方法，GET 或 POST
     * @param sendData 发送给微信服务器的数据
     * @return JSONObject 实例
     * @throws WeixinException 抛出微信异常
     */
    public static JSONObject sendHttps(String url, String method, String sendData) throws WeixinException {
        if (StringUtils.isBlank(url) || StringUtils.isBlank(method)) {
            return null;
        }

        method = method.toUpperCase(Locale.getDefault());

        if (!Constants.REQUEST_GET.equals(method) && !Constants.REQUEST_POST.equals(method)) {
            return null;
        }

        // 声明 HTTPS 请求
        HttpsURLConnection httpsURLConnection = null;
        // 声明输入流
        InputStream httpsURLConnectionIn = null;
        InputStreamReader httpsURLConnectionInReader = null;
        BufferedReader httpsURLConnectionBufReader = null;

        try {
            // 证书信任管理器数组
            TrustManager[] trustManagers = {new MyX509TrustManager()};
            // SSL 上下文
            SSLContext sslContext = SSLContext.getInstance(SSL, SUN_JSSE);
            sslContext.init(null, trustManagers, new SecureRandom());

            // 获取 SSL Socket 工厂
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            // 创建 url 对象
            URL urlObj = new URL(url);

            httpsURLConnection = (HttpsURLConnection) urlObj.openConnection();
            httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
            httpsURLConnection.setDoOutput(true);
            httpsURLConnection.setDoInput(true);
            httpsURLConnection.setUseCaches(false);
            httpsURLConnection.setRequestMethod(method);
            httpsURLConnection.connect();

            // POST 请求，发送数据给微信服务器
            if (Constants.REQUEST_POST.equals(method) && StringUtils.isNotBlank(sendData)) {
                OutputStream httpsURLConnectionOut = httpsURLConnection.getOutputStream();

                httpsURLConnectionOut.write(sendData.getBytes(Constants.UTF_8));
                httpsURLConnectionOut.close();
            }

            // 接收微信服务器返回的数据
            httpsURLConnectionIn = httpsURLConnection.getInputStream();
            httpsURLConnectionInReader = new InputStreamReader(httpsURLConnectionIn, Constants.UTF_8);
            httpsURLConnectionBufReader = new BufferedReader(httpsURLConnectionInReader);

            // 数据缓冲区
            StringBuffer dataSb = new StringBuffer();
            // 一行
            String line = null;

            while (StringUtils.isNotBlank((line = httpsURLConnectionBufReader.readLine()))) {
                dataSb.append(line);
            }

            // 最终数据
            String data = dataSb.toString();

            log.info("调用微信接口：{}--->参数：{}，返回数据：{}", url, sendData, data);

            return FastJsonUtils.json2JSONObject(data);

            // 捕获异常，并抛出
        } catch (Exception e) {
            log.error("调用微信接口：{}-->出现异常", url);

            throw new WeixinException("调用微信接口出现异常", e);

            // 释放资源
        } finally {
            try {
                if (httpsURLConnectionBufReader != null) {
                    httpsURLConnectionBufReader.close();
                }

                if (httpsURLConnectionInReader != null) {
                    httpsURLConnectionInReader.close();
                }

                if (httpsURLConnectionIn != null) {
                    httpsURLConnectionIn.close();
                }
            } catch (IOException e) {
            }

            // 断开连接
            if (httpsURLConnection != null) {
                httpsURLConnection.disconnect();
            }
        }
    }

    /**
     * 发送 HTTPS GET 请求。
     *
     * @param url url
     * @return JSONObject 实例
     * @throws WeixinException 抛出微信异常
     */
    public static JSONObject sendHttpsGet(String url) throws WeixinException {
        return sendHttps(url, Constants.REQUEST_GET, null);
    }

    /**
     * 发送 HTTPS POST 请求。
     *
     * @param url      url
     * @param sendData 发送给微信服务器的数据
     * @return JSONObject 实例
     * @throws WeixinException 抛出微信异常
     */
    public static JSONObject sendHttpsPost(String url, String sendData) throws WeixinException {
        return sendHttps(url, Constants.REQUEST_POST, sendData);
    }

    /**
     * 发送携带证书的 HTTPS 请求。
     *
     * @param url      url
     * @param method   请求方法，GET 或 POST
     * @param sendData 发送给微信服务器的数据
     * @return 字符串
     * @throws WeixinException 抛出微信异常
     */
    public static String sendCertHttps(String url, String method, String sendData) throws WeixinException {
        if (StringUtils.isBlank(url) || StringUtils.isBlank(method)) {
            return Constants.EMPTY_STR;
        }

        method = method.toUpperCase(Locale.getDefault());

        if (!Constants.REQUEST_GET.equals(method) && !Constants.REQUEST_POST.equals(method)) {
            return Constants.EMPTY_STR;
        }

        // 声明输入流
        FileInputStream fileInputStream = null;
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;

        try {
            // 读取 PKCS12 证书文件
            fileInputStream = new FileInputStream(new File(String.format("%s%s", WeixinUtils.class.getClassLoader().getResource(Constants.EMPTY_STR).getPath(), APICLIENT_CERT_P12_PATH)));

            // 指定读取证书格式为 PKCS12
            KeyStore keyStore = KeyStore.getInstance(PKCS12);
            // 加载证书，指定 PKCS12 的密码（商户 ID）
            keyStore.load(fileInputStream, MCH_ID.toCharArray());

            // 指定 SSLContext
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, MCH_ID.toCharArray()).build();
            // 指定 TLS 版本
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{TLSV1}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            // 设置 httpClient 的 SSLSocketFactory
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            // 响应
            CloseableHttpResponse response = null;

            // GET 请求
            if (Constants.REQUEST_GET.equals(method)) {
                HttpGet httpGet = new HttpGet(url);
                httpGet.addHeader(Constants.CONTENT_TYPE, Constants.CONTENT_TYPE_VALUE_1);

                // 执行请求，获取响应
                response = httpClient.execute(httpGet);

                // POST 请求
            } else {
                HttpPost httpPost = new HttpPost(url);
                // 设置请求头
                httpPost.addHeader(Constants.CONTENT_TYPE, Constants.CONTENT_TYPE_VALUE_1);
                // 设置发送的数据
                httpPost.setEntity(new StringEntity(Utils.encode(sendData, Constants.ISO_8859_1)));

                // 执行请求，获取响应
                response = httpClient.execute(httpPost);
            }

            // 获取响应实体
            HttpEntity httpEntity = response.getEntity();

            inputStream = httpEntity.getContent();
            inputStreamReader = new InputStreamReader(inputStream, Constants.UTF_8);
            bufferedReader = new BufferedReader(inputStreamReader);

            // 数据缓冲区
            StringBuffer dataSb = new StringBuffer();
            // 一行
            String line = null;

            while (StringUtils.isNotBlank((line = bufferedReader.readLine()))) {
                dataSb.append(line);
            }

            // 最终数据
            String data = dataSb.toString();

            log.info("调用微信接口：{}--->参数：{}，返回数据：{}", url, sendData, data);

            return data;

            // 捕获异常，并抛出
        } catch (Exception e) {
            log.error("调用微信接口：{}-->出现异常", url);

            throw new WeixinException("调用微信接口出现异常", e);

            // 释放资源
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }

                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }

                if (inputStream != null) {
                    inputStream.close();
                }

                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }

    /**
     * 发送携带证书的 HTTPS GET 请求。
     *
     * @param url url
     * @return JSONObject 实例
     * @throws WeixinException 抛出微信异常
     */
    public static String sendCertHttpsGet(String url) throws WeixinException {
        return sendCertHttps(url, Constants.REQUEST_GET, null);
    }

    /**
     * 发送携带证书的 HTTPS POST 请求。
     *
     * @param url      url
     * @param sendData 发送给微信服务器的数据
     * @return JSONObject 实例
     * @throws WeixinException 抛出微信异常
     */
    public static String sendCertHttpsPost(String url, String sendData) throws WeixinException {
        return sendCertHttps(url, Constants.REQUEST_POST, sendData);
    }

    /**
     * 解密普通用户发送给公众账号的消息。
     *
     * @param msgSignature 消息加密签名
     * @param timestamp    时间戳
     * @param nonce        随机数
     * @param encryptMsg   密文消息
     * @return 微信指定格式的 XML 字符串（明文）
     * @throws WeixinException 抛出微信异常
     */
    public static String decryptMsg(String msgSignature, String timestamp, String nonce, String encryptMsg) throws WeixinException {
        try {
            if (StringUtils.isBlank(msgSignature) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonce)) {
                throw new WeixinException(String.format("解密普通用户发送给公众账号的消息的参数不能为空--->msgSignature[%s], timestamp[%s], nonce[%s], encryptMsg[%s]", msgSignature, timestamp, nonce, encryptMsg));
            }

            log.debug("解密普通用户发送给公众账号的消息的参数--->msgSignature[%s], timestamp[%s], nonce[%s]", msgSignature, timestamp, nonce);
            log.debug("解密普通用户发送给公众账号的消息，解密前--->密文[%s]", encryptMsg);

            // 将密文消息 XML 格式化
            String encryptMsgXML = String.format(ENCRYPT_XML_FORMAT, encryptMsg);
            // 初始化
            WXBizMsgCrypt pc = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);
            // 解密
            String decryptMsg = pc.decryptMsg(msgSignature, timestamp, nonce, encryptMsgXML);

            log.debug("解密普通用户发送给公众账号的消息，解密前--->明文[%s]", decryptMsg);

            return decryptMsg;
        } catch (Exception e) {
            throw new WeixinException("解密普通用户发送给公众账号的消息出现异常", e);
        }
    }

    /**
     * 加密公众账号发送给普通用户的消息。
     *
     * @param decryptMsg 明文消息，即公众账号发送给普通用户的消息
     * @param timestamp  时间戳
     * @param nonce      随机数
     * @return 微信指定格式的 XML 字符串（密文）
     * @throws WeixinException 抛出微信异常
     */
    public static String encryptMsg(String decryptMsg, String timestamp, String nonce) throws WeixinException {
        try {
            if (StringUtils.isBlank(decryptMsg) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonce)) {
                throw new WeixinException(String.format("加密公众账号发送给普通用户的消息的参数不能为空--->decryptMsg[%s], timestamp[%s], nonce[%s]", decryptMsg, timestamp, nonce));
            }

            log.debug("加密公众账号发送给普通用户的消息的参数--->timestamp[%s], nonce[%s]", timestamp, nonce);
            log.debug("加密公众账号发送给普通用户的消息，加密前--->明文[%s]", decryptMsg);

            // 初始化
            WXBizMsgCrypt pc = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APP_ID);
            // 加密
            String encryptMsg = pc.encryptMsg(decryptMsg, timestamp, nonce);

            log.debug("加密公众账号发送给普通用户的消息，加密后--->密文[%s]", encryptMsg);

            return encryptMsg;
        } catch (Exception e) {
            throw new WeixinException("加密公众账号发送给普通用户的消息出现异常", e);
        }
    }

    /**
     * 将各种回复消息转换为微信指定格式的 Xml 字符串。<br>
     * 这些消息包括：
     * <ol>
     * <li>文本消息；</li>
     * <li>图片消息；</li>
     * <li>语音消息；</li>
     * <li>视频消息；</li>
     * <li>音乐消息；</li>
     * <li>图文消息。</li>
     * </ol>
     *
     * @param t 消息实例
     * @return 微信指定格式的 Xml 字符串
     * @throws WeixinException 转换失败
     */
    public static <T> String msg2Xml(T t) throws WeixinException {
        // 只能转换回复消息
        if ((t == null) || !(t instanceof BaseMsg)) {
            return Constants.EMPTY_STR;
        }

        try {
            XStream xStream = new XStream(new MyXppDriver());

            xStream.alias(XML_STR, t.getClass());

            // 如果是图文消息，需要添加 item 节点
            if (t instanceof NewsMsg) {
                xStream.alias("item", new Article().getClass());
            }

            return xStream.toXML(t);
        } catch (Exception e) {
            throw new WeixinException(String.format("回复消息[%s]转换为微信指定格式的Xml字符串出现异常", t.getClass().getName()), e);
        }
    }

    /**
     * 获取微信发普通红包的签名。
     *
     * @param weixinSendredpackParamVo 微信发普通红包参数映射实例
     * @return 微信发普通红包的签名
     */
    public static String getWeixinSendredpackSign(WeixinSendredpackParamVo weixinSendredpackParamVo) {
        if (weixinSendredpackParamVo == null) {
            return Constants.EMPTY_STR;
        }

        // 参数 map
        Map<String, Object> paramMap = new TreeMap<String, Object>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getNonce_str())) {
            paramMap.put(NONCE_STR_STR, weixinSendredpackParamVo.getNonce_str());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getMch_billno())) {
            paramMap.put(MCH_BILLNO_STR, weixinSendredpackParamVo.getMch_billno());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getMch_id())) {
            paramMap.put(MCH_ID_STR, weixinSendredpackParamVo.getMch_id());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getWxappid())) {
            paramMap.put(WXAPPID_STR, weixinSendredpackParamVo.getWxappid());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getSend_name())) {
            paramMap.put(SEND_NAME_STR, weixinSendredpackParamVo.getSend_name());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getRe_openid())) {
            paramMap.put(RE_OPENID_STR, weixinSendredpackParamVo.getRe_openid());
        }

        if (weixinSendredpackParamVo.getTotal_amount() != null) {
            paramMap.put(TOTAL_AMOUNT_STR, weixinSendredpackParamVo.getTotal_amount());
        }

        if (weixinSendredpackParamVo.getTotal_num() != null) {
            paramMap.put(TOTAL_NUM_STR, weixinSendredpackParamVo.getTotal_num());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getWishing())) {
            paramMap.put(WISHING_STR, weixinSendredpackParamVo.getWishing());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getClient_ip())) {
            paramMap.put(CLIENT_IP_STR, weixinSendredpackParamVo.getClient_ip());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getAct_name())) {
            paramMap.put(ACT_NAME_STR, weixinSendredpackParamVo.getAct_name());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getRemark())) {
            paramMap.put(REMARK_STR, weixinSendredpackParamVo.getRemark());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getScene_id())) {
            paramMap.put(SCENE_ID_STR, weixinSendredpackParamVo.getScene_id());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getRisk_info())) {
            paramMap.put(RISK_INFO_STR, weixinSendredpackParamVo.getRisk_info());
        }

        if (StringUtils.isNotBlank(weixinSendredpackParamVo.getConsume_mch_id())) {
            paramMap.put(CONSUME_MCH_ID_STR, weixinSendredpackParamVo.getConsume_mch_id());
        }

        return Utils.md5(String.format("%s&key=%s", Utils.map2UrlParam(paramMap), WEIXIN_PAY_KEY)).toUpperCase();
    }

    /**
     * 获取微信查询红包记录的签名。
     *
     * @param weixinGethbinfoParamVo 微信查询红包记录参数映射实例
     * @return 微信查询红包记录的签名
     */
    public static String getWeixinGethbinfoSign(WeixinGethbinfoParamVo weixinGethbinfoParamVo) {
        if (weixinGethbinfoParamVo == null) {
            return Constants.EMPTY_STR;
        }

        // 参数 map
        Map<String, Object> paramMap = new TreeMap<String, Object>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });

        if (StringUtils.isNotBlank(weixinGethbinfoParamVo.getNonce_str())) {
            paramMap.put(NONCE_STR_STR, weixinGethbinfoParamVo.getNonce_str());
        }

        if (StringUtils.isNotBlank(weixinGethbinfoParamVo.getMch_billno())) {
            paramMap.put(MCH_BILLNO_STR, weixinGethbinfoParamVo.getMch_billno());
        }

        if (StringUtils.isNotBlank(weixinGethbinfoParamVo.getMch_id())) {
            paramMap.put(MCH_ID_STR, weixinGethbinfoParamVo.getMch_id());
        }

        if (StringUtils.isNotBlank(weixinGethbinfoParamVo.getAppid())) {
            paramMap.put(APPID_STR, weixinGethbinfoParamVo.getAppid());
        }

        if (StringUtils.isNotBlank(weixinGethbinfoParamVo.getBill_type())) {
            paramMap.put(BILL_TYPE_STR, weixinGethbinfoParamVo.getBill_type());
        }

        return Utils.md5(String.format("%s&key=%s", Utils.map2UrlParam(paramMap), WEIXIN_PAY_KEY)).toUpperCase();
    }
}
